#pragma once

#include <zenox/glm.hpp>
#include <string_view>

namespace zenoui {

struct Color {
    glm::vec4 m_rgba;

    static float _pow22(float x) {
        return std::pow(x, 2.2f);
    }

    static float _powinv22(float x) {
        return std::pow(x, 1.0f / 2.2f);
    }

    static float _i2f(uint8_t x) {
        return _pow22((float)x * (1.0f / 255.0f));
    }

    static uint8_t _f2i(float x) {
        return (uint8_t)std::clamp(std::floor(_powinv22(x) * 255.0f + 0.5f), 0.0f, 255.0f);
    }

    static uint8_t _hex2i(char c) {
        if ('0' <= c && c <= '9') {
            return c - '0';
        }
        if ('a' <= c && c <= 'f') {
            return (c - 'a') + 10;
        }
        if ('A' <= c && c <= 'F') {
            return (c - 'A') + 10;
        }
        return 0;
    }

    static uint8_t _hex2i(char const *c) {
        return _hex2i(c[0]) * 16 + _hex2i(c[1]);
    }

    Color() = default;
    Color(glm::vec4 const &rgba) : m_rgba(rgba) {}
    Color(glm::vec3 const &rgb) : m_rgba(rgb, 1.0f) {}
    Color(float r, float g, float b, float a = 1.0f) : m_rgba(r, g, b, a) {}

    static Color fromRGB(float r, float g, float b) {
        return Color{r, g, b};
    }

    static Color fromRGBA(float r, float g, float b, float a) {
        return Color{r, g, b, a};
    }

    glm::vec3 toRGB() const {
        return glm::vec3(m_rgba);
    }

    glm::vec4 toRGBA() const {
        return m_rgba;
    }

    glm::u8vec3 toSRGB() const {
        return {_f2i(m_rgba.r), _f2i(m_rgba.g), _f2i(m_rgba.b)};
    }

    glm::u8vec4 toSRGBA() const {
        return {_f2i(m_rgba.r), _f2i(m_rgba.g), _f2i(m_rgba.b), _f2i(m_rgba.a)};
    }

    static Color fromSRGB(glm::u8vec3 srgb) {
        return fromSRGB(_i2f(srgb.r), _i2f(srgb.g), _i2f(srgb.b));
    }

    static Color fromSRGBA(glm::u8vec4 srgb) {
        return fromSRGBA(_i2f(srgb.r), _i2f(srgb.g), _i2f(srgb.b), _i2f(srgb.a));
    }

    static Color fromSRGB(uint8_t r, uint8_t g, uint8_t b) {
        return Color{_i2f(r), _i2f(g), _i2f(b)};
    }

    static Color fromSRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
        return Color{_i2f(r), _i2f(g), _i2f(b), _i2f(a)};
    }

    static Color fromHexString(std::string_view hex) {
        if (hex.starts_with('#'))
            hex.remove_prefix(1);
        switch (hex.size()) {
        case 3:
            return fromSRGB(_hex2i(hex[0]), _hex2i(hex[1]), _hex2i(hex[2]));
        case 6:
            return fromSRGB(_hex2i(&hex[0]), _hex2i(&hex[2]), _hex2i(&hex[4]));
        case 4:
            return fromSRGBA(_hex2i(hex[0]), _hex2i(hex[1]), _hex2i(hex[2]), _hex2i(hex[3]));
        case 8:
            return fromSRGBA(_hex2i(&hex[0]), _hex2i(&hex[2]), _hex2i(&hex[4]), _hex2i(&hex[6]));
        default:
            return Color{};
        }
    }

    explicit Color(std::string_view hex) {
        *this = fromHexString(hex);
    }

    float *data() {
        return glm::value_ptr(m_rgba);
    }

    float const *data() const {
        return glm::value_ptr(m_rgba);
    }
};

Color getPaletteColor(int kind /* 0,1,2,3 */, int index /* 0,1,2,3,4 */);

}
